<?php
/**
 * TestLink Open Source Project - http://testlink.sourceforge.net/
 * This script is distributed under the GNU General Public License 2 or later.
 *
 * @package    TestLink
 * @author     Andreas Simon
 * @copyright  2006-2010, TestLink community
 * @version    CVS: $Id: tlTestCaseFilterControl.class.php,v 1.33.2.1 2010/11/10 17:46:09 asimon83 Exp $
 * @link       http://www.teamst.org/index.php
 * @filesource http://testlink.cvs.sourceforge.net/viewvc/testlink/testlink/lib/functions/tlTestCaseFilterControl.class.php?view=markup
 *
 * This class extends tlFilterPanel for the specific use with test case tree.
 * It holds the logic to be used at GUI level to manage a common set of settings and filters for test cases.
 * 
 * This class is used from different navigator-frames (left frames with a test case tree in it)
 * with different modes for different features.
 * This is a little overview about its usage in TestLink:
 * 
 * - planTCNavigator.php/tpl use it in "plan_mode" for these features:
 *    --> assign test case execution
 *    --> update linked test case versions
 *    --> set urgent tests
 * 
 * - execNavigator.php/tpl in "execution_mode" 
 *    --> test execution
 * 
 * - planAddTCNavigator.php/tpl in "plan_add_mode"
 *    --> add/remove test cases
 * 
 * - listTestCases.php/tcTree.tpl in "edit_mode"
 *    --> edit test specification
 *    --> assign keywords
 *    --> assign requirements
 *
 * @internal Revisions:
 *
 * 20101110 - asimon - BUGID 3822: Keywords combobox is absent on the Filters pane of 'Add / Remove Test Cases'
 * 20101103 - asimon - custom fields on test spec did not retain value after apply
 * 20101028 - asimon - BUGID 3933: Add test case to test plan - Left Pane filter uses 
 *                     priority concept to filter test spec where priority does not exist
 * 20101026 - asimon - BUGID 3930: changing date format according to given locale
 * 20101025 - asimon - BUGID 3716: date pull downs changed to calendar interface
 * 20101019 - asimon - BUGID 3910: show filter only if test priority management is enabled
 * 20101011 - asimon - BUGID 3883: fixed handling of unset date custom field inputs
 * 20101011 - asimon - BUGID 3884: added handling for datetime custom fields
 * 20101005 - asimon - BUGID 3853: show_filters disabled still shows panel
 * 20100929 - asimon - BUGID 3817
 * 20100972 - asimon - additional fix to BUGID 3809
 * 20100927 - amitkhullar - BUGID 3809 - Radio button based Custom Fields not working
 * 20100901 - asimon - show button "show/hide cf" only when there are cfields
 * 20100901 - asimon - re-enabled filter for assigned user when assigning testcases
 * 20100901 - asimon - re-enable option "user_filter_default"
 * 20100830 - asimon - BUGID 3726: store user's selection of build and platform
 * 20100811 - asimon - BUGID 3566: show/hide CF
 * 20100810 - asimon - added TC ID filter for Test Cases
 * 20100807 - franciscom - BUGID 3660
 * 20100727 - asimon - BUGID 3630 - syntax error in get_argument_string()
 * 20100716 - asimon - BUGID 3406 - changes on init_settings() and $mode_setting_mapping
 * 20100713 - asimon - fixed Drag&Drop error caused by init_filter_custom_fields()
 * 20100702 - asimon - fixed error in init_setting_testplan()
 * 20100701 - asimon - BUGID 3414 - additional work in init_filter_custom_fields()
 * 20100628 - asimon - removal of constants
 * 20100624 - asimon - CVS merge (experimental branch to HEAD)
 * 20100503 - asimon - start of implementation of filter panel class hierarchy
 *                     to simplify/generalize filter panel handling
 *                     for test cases and requirements
 */

/*
 * --------------------------------------------------------
 * An important note on BUGID 3516 (request-URL too large):
 * --------------------------------------------------------
 * 
 * That problem has been solved by attaching some data (the set of active filters, settings and
 * testcase IDs to show if filtering has been done) to session.
 * 
 * Since a user can have the same feature open in multiple tabs, that alone is not enough to
 * solve this issue. When a user opens e.g. the test case execution page and sets filter options
 * there, then opens the same page in another tab, the data saved in session would also be
 * applied to this second tab although no filter options have been set there yet by the user.
 * 
 * This has now been solved by a so called form token. This token is, on first opening of a
 * navigator frame, generated by the method generate_form_token() and then stored in a member
 * variable with the name $form_token. This token will be stored in an identically named hidden 
 * input field within the HTML filter form, so it gets sent by POST to every called page.
 * It is also attached to the GET argument string returned by get_argument_string() that gets 
 * passed to multiple JavaScript functions, which are used to open nodes from the tree in the 
 * left frame in a new page in the right frame.
 * 
 * So the token is used to identify (from pages within the right frame) the data that got stored
 * for them in session by the navigator page in the left frame.
 * If the navigator page calls itself (when the user presses one of the submit buttons in the form),
 * it sends the stored token via POST to itself. So the same token can be used again to store data
 * in session, instead of generating a new token blindly on every page call no matter where the
 * call comes from. But if the user opens a new tab, the new navigator page knows this because
 * no token has been sent to it - so it generates a new one.
 * 
 * The data is saved in session in the form of an array like this example:
 * 
 * [execution_mode] => Array                              // "mode" used by navigator
 *   (
 *     [1986901204] => Array                              // form token to identify the correct tab
 *       (
 *         [filter_keywords_filter_type] => Or            // the active filters and settings,
 *         [filter_result_result] => f                    // prefixed with "filter_" and "setting_"
 *         [filter_result_method] => 3
 *         [filter_result_build] => 71
 *         [filter_assigned_user_include_unassigned] => 1
 *         [filter_testcase_name] => 
 *         [filter_toplevel_testsuite] => Array
 *           (
 *           )
 *
 *         [filter_keywords] => 
 *         [filter_priority] => 3
 *         [filter_execution_type] => 2
 *         [filter_assigned_user] => Array
 *           (
 *             [3] => 3
 *           )
 * 
 *         [filter_custom_fields] => 
 *         [setting_testplan] => 4990
 *         [setting_build] => 71
 *         [setting_platform] => 
 *         [setting_refresh_tree_on_action] => 1
 *         [testcases_to_show] => Array                   // The internal IDs of the test cases which
 *           (                                            // where not filtered out by user's choices.
 *             [0] => 1852                                // This was the part which earlier caused
 *             [1] => 60                                  // the error because of the too long URL.
 *             [2] => 2039
 *             [3] => 2033
 *             [4] => 2065
 *             [5] => 2159
 *             [6] => 3733
 *           )
 *
 *         [timestamp] => 1277727920                      // additional means to check age of session data
 *       )
 *   )
 * 
 * The access to this data can be done in the following way from the right frame page:
 * 
 * $form_token = isset($_REQUEST['form_token']) ? $_REQUEST['form_token'] : 0;
 * $mode = 'execution_mode';
 * $session_data = isset($_SESSION[$mode]) && isset($_SESSION[$mode][$form_token])
 *                 ? $_SESSION[$mode][$form_token] : null;
 * 
 * The variable $session_data then holds the array with all the active filters,
 * settings and filtered test case IDs in it, or is null if nothing has been stored yet
 * in the session.
 * 
 * But now we have another problem:
 * There can be one array for each mode in the session. In each of these arrays is a set of
 * further arrays with the form tokens as keys and the filter information in it.
 * If a user now opens the same page more than once in a row (by switching back and forth 
 * between features or by using the same feature in multiple tabs) there can be more and more
 * arrays with filter information in this set of arrays.
 * 
 * Because of this, an additional timestamp is written into each of these information arrays.
 * On each storage process that writes information into the session triggered by a call 
 * to a navigator page, the timestamp gets refreshed if an old token has been reused or
 * it gets created with the creation of a new data array.
 * 
 * This timestamp can be used to delete old arrays with information that is not needed anymore.
 * Since we have no means to otherwise detect the case that a user has closed the tab 
 * and doesn't need this information in the session anymore, we have to determine the age of 
 * those arrays with the timestamp and delete everything that is older than a certain given 
 * threshold. This is done by the method delete_old_session_data() which is automatically called
 * from the contstructor of this class. It checks the age of all the saved 
 * arrays inside the array for the active mode and then deletes everything that's older than
 * the given threshold. This threshold can be passed as a parameter to the method, otherwise a
 * default value of one hour is used.
 * 
 * If a user logs out of TestLink, of course all this data in the session is deleted,
 * no matter if the one hour threshold has passed or not.
 * ------------------------------------------------------------------------------------------------
 */

/**
 * This class extends tlFilterPanel for the specific use with the testcase tree.
 * It contains logic to be used at GUI level to manage
 * a common set of settings and filters for testcases.
 *
 * @author Andreas Simon
 * @package TestLink
 * @uses testplan
 * @uses exec_cf_mgr
 * @uses tlPlatform
 * @uses testcase
 */
class tlTestCaseFilterControl extends tlFilterControl {

	/**
	 * Testcase manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var testcase
	 */
	private $tc_mgr = null;
	
	/**
	 * Platform manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var tlPlatform
	 */
	private $platform_mgr = null;
	
	/**
	 * Custom field manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var exec_cf_mgr
	 */
	private $cfield_mgr = null;
	
	/**
	 * Testplan manager object.
	 * Initialized not in constructor, only on first use to save resources.
	 * @var testplan
	 */
	private $testplan_mgr = null;
	
	/**
	 * This array contains all possible filters.
	 * It is used as a helper to iterate over all the filters in some loops.
	 * It also sets options how and from where to load the parameters with
	 * input fetching functions in init_args()-method.
	 * Its keys are the names of the settings (class constants are used),
	 * its values are the arrays for the input parser.
	 * @var array
	 */
	private $all_filters = array('filter_tc_id' => array("POST", tlInputParameter::STRING_N),
	                             'filter_testcase_name' => array("POST", tlInputParameter::STRING_N),
	                             'filter_toplevel_testsuite' => array("POST", tlInputParameter::STRING_N),
	                             'filter_keywords' => array("POST", tlInputParameter::ARRAY_INT),
	                             'filter_priority' => array("POST", tlInputParameter::INT_N),
	                             'filter_execution_type' => array("POST", tlInputParameter::INT_N),
	                             'filter_assigned_user' => array("POST", tlInputParameter::ARRAY_INT),
	                             'filter_custom_fields' => array("POST", tlInputParameter::ARRAY_STRING_N),
	                             'filter_result' => null); // result: no info here, divided into more parts

	/**
	 * This array is used as an additional security measure. It maps all available
	 * filters to the mode in which they can be used. If a user tries to
	 * enable filters in config.inc.php which are not defined inside this array,
	 * this will be simply ignored instead of trying to initialize the filter
	 * no matter wether it has been implemented or not.
	 * The keys inside this array are the modes defined above as class constants.
	 * So it can be checked if a filter is available in a given mode without
	 * relying only on the config parameter.
	 * @var array
	 */
	private $mode_filter_mapping = array('edit_mode' => array('filter_tc_id',
	                                                          'filter_testcase_name',
	                                                          'filter_toplevel_testsuite',
	                                                          'filter_keywords',
	                                                          'filter_execution_type',
	                                                          'filter_custom_fields'),
	                                     'execution_mode' => array('filter_tc_id',
	                                                               'filter_testcase_name',
	                                                               'filter_toplevel_testsuite',
	                                                               'filter_keywords',
	                                                               'filter_priority',
	                                                               'filter_execution_type',
	                                                               'filter_assigned_user',
	                                                               'filter_custom_fields',
	                                                               'filter_result'),
	                                     'plan_mode' => array('filter_tc_id',
	                                                          'filter_testcase_name',
	                                                          'filter_toplevel_testsuite',
	                                                          'filter_keywords',
	                                                          'filter_priority',
	                                                          'filter_execution_type',
		                                                      // enabled user filter when assigning testcases
		                                                      'filter_assigned_user',
	                                                          'filter_custom_fields',
	                                                          'filter_result'),
	                                     'plan_add_mode' => array('filter_tc_id',
	                                                              'filter_testcase_name',
	                                                              'filter_toplevel_testsuite',
	                                                              'filter_keywords',
	                                                              // BUGID 3933 - no priority here
		                                                          //'filter_priority',
	                                                              'filter_execution_type',
	                                                              'filter_custom_fields'));

	/**
	 * This array contains all possible settings. It is used as a helper
	 * to later iterate over all possibilities in loops.
	 * Its keys are the names of the settings, its values the arrays for the input parser.
	 * @var array
	 */
	private $all_settings = array('setting_testplan' => array("POST", tlInputParameter::INT_N),
	                              'setting_build' => array("POST", tlInputParameter::INT_N),
	                              'setting_platform' => array("POST", tlInputParameter::INT_N),
	                              'setting_refresh_tree_on_action' => array("POST", tlInputParameter::CB_BOOL));

	/**
	 * This array is used to map the modes to their available settings.
	 * @var array
	 */
	private $mode_setting_mapping = array('edit_mode' => array('setting_refresh_tree_on_action'),
	                                      'execution_mode' => array('setting_testplan',
	                                                                'setting_build',
	                                                                'setting_platform',
	                                                                'setting_refresh_tree_on_action'),
	                                      'plan_mode' => array('setting_testplan',
	                                                           // BUGID 3406
	                                                           'setting_build',
	                                                           'setting_refresh_tree_on_action'),
	                                      'plan_add_mode' => array('setting_testplan',
	                                                               'setting_refresh_tree_on_action'));

	/**
	 * The mode used. Depending on the feature for which this class will be instantiated.
	 * This mode defines which filter configuration will be loaded from config.inc.php
	 * and therefore which filters will be loaded and used for the templates.
	 * Value has to be one of the class constants for mode, default is edit mode.
	 * @var string
	 */
	private $mode = 'edit_mode';

	/**
	 * The token that will be used to identify the relationship between left frame
	 * (with navigator) and right frame (which displays execution of test case e.g.) in session.
	 * @var string
	 */
	public $form_token = null;
	
	/**
	 *
	 * @param database $dbHandler
	 * @param string $mode can be edit_mode/execution_mode/plan_mode/plan_add_mode, depending on usage
	 */
	public function __construct(&$dbHandler, $mode = 'edit_mode') {

		// set mode to define further actions before calling parent constructor
		$this->mode = array_key_exists($mode,$this->mode_filter_mapping) ? $mode : 'edit_mode';

		// Call to constructor of parent class tlFilterControl.
		// This already loads configuration and user input
		// and does all the remaining necessary method calls,
		// so no further method call is required here for initialization.
		parent::__construct($dbHandler);

		// delete any filter settings that may be left from previous calls in session
		$this->delete_own_session_data();
		$this->delete_old_session_data();
		
		$this->save_session_data();
	}

	public function __destruct() {
		parent::__destruct(); //destroys testproject manager
		
		// destroy member objects
		unset($this->tc_mgr);
		unset($this->testplan_mgr);
		unset($this->platform_mgr);
		unset($this->cfield_mgr);
	}

	/**
	 * Reads the configuration from the configuration file specific for test cases,
	 * additionally to those parts of the config which were already loaded by parent class.
	 *
	 */
	protected function read_config() {

		// some configuration reading already done in parent class
		parent::read_config();

		// load configuration for active mode only
		$this->configuration = config_get('tree_filter_cfg')->testcases->{$this->mode};

		// load also exec config - it is not only needed in exec mode
		$this->configuration->exec_cfg = config_get('exec_cfg');

		// some additional testcase configuration
		$this->configuration->tc_cfg = config_get('testcase_cfg');
		
		// is choice of advanced filter mode enabled?
    	if (isset($this->configuration->advanced_filter_mode_choice)
    	&& $this->configuration->advanced_filter_mode_choice == ENABLED) {
    		$this->filter_mode_choice_enabled = true;
    	} else {
    		$this->filter_mode_choice_enabled = false;
    	}
		
		return tl::OK;
	} // end of method

	/**
	 * Does what init_args() usually does in all scripts: Reads the user input
	 * from request ($_GET and $_POST). Later configuration,
	 * settings and filters get modified according to that user input.
	 */
	protected function init_args() {
		
		// some common user input is already read in parent class
		parent::init_args();

		// add settings and filters to parameter info array for request parsers
		$params = array();
		foreach ($this->all_settings as $name => $info) {
			if (is_array($info)) {
				$params[$name] = $info;
			}
		}
		foreach ($this->all_filters as $name => $info) {
			if (is_array($info)) {
				$params[$name] = $info;
			}
		}
		I_PARAMS($params, $this->args);

		$type = 'filter_keywords_filter_type';
		$this->args->{$type} = (isset($_REQUEST[$type])) ? trim($_REQUEST[$type]) : 'Or';

		$extra_keys = array('filter_result_result',
		                    'filter_result_method',
		                    'filter_result_build');

		foreach ($extra_keys as $ek) {
			$this->args->{$ek} = (isset($_REQUEST[$ek])) ? $_REQUEST[$ek] : null;
		}

		$this->args->{'filter_assigned_user_include_unassigned'} = 
			isset($_REQUEST['filter_assigned_user_include_unassigned']) ? 1 : 0;

		// got session token sent by form or do we have to generate a new one?
		$sent_token = null;
		$this->args->form_token = null;
		if (isset($_REQUEST['form_token'])) {
			// token got sent
			$sent_token = $_REQUEST['form_token'];
		}
		if (!is_null($sent_token) && isset($_SESSION[$this->mode][$sent_token])) {
			// sent token is valid
			$this->form_token = $sent_token;
			$this->args->form_token = $sent_token;
		} else {
			$this->generate_form_token();
		}
		
		// "feature" is needed for plan and edit modes
		$this->args->feature = isset($_REQUEST['feature']) ? trim($_REQUEST['feature']) : null;
		
		switch ($this->mode) {
			
			case 'plan_mode':
				switch($this->args->feature) {
					case 'planUpdateTC':
					case 'test_urgency':
					case 'tc_exec_assignment':
						// feature OK
					break;
				
					default:
						// feature not OK
						tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
						exit();
					break;
				}
			break;
			
			case 'edit_mode':
				switch($this->args->feature) {
					case 'edit_tc':
					case 'keywordsAssign':
					case 'assignReqs':
						// feature OK
					break;
				
					default:
						// feature not OK
						tLog("Wrong or missing GET argument 'feature'.", 'ERROR');
						exit();
					break;
				}
			break;
		}
	    
	} // end of method

	/**
	 * Initializes all settings.
	 * Iterates through all available settings and adds an array to $this->settings
	 * for the active ones, sets the rest to false so this can be
	 * checked from templates and elsewhere.
	 * Then calls the initializing method for each still active setting.
	 */
	protected function init_settings() {
		$at_least_one_active = false;

		foreach ($this->all_settings as $name => $info) {
			$init_method = "init_$name";
			if (in_array($name, $this->mode_setting_mapping[$this->mode])
			&& method_exists($this, $init_method)) {
				// is valid, configured, exists and therefore can be used, so initialize this setting
				$this->$init_method();
				$at_least_one_active = true;
			} else {
				// is not needed, simply deactivate it by setting it to false in main array
				$this->settings[$name] = false;
			}
		}
		
		// special situation: the build setting is in plan mode only needed for one feature
		// BUGID 3406
		if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') {
			$this->settings['setting_build'] = false;
		}
		
		// if at least one active setting is left to display, switch settings panel on
		if ($at_least_one_active) {
			$this->display_settings = true;
		}
	}

	/**
	 * Initialize all filters. (called by parent::__construct())
	 * I'm double checking here with loaded configuration _and_ additional array
	 * $mode_filter_mapping, set according to defined mode, because this can avoid errors in case
	 * when users try to enable a filter in config that doesn't exist for a mode.
	 * Effect: Only existing and implemented filters can be activated in config file.
	 */
	protected function init_filters() {
		// In resulting data structure, all values have to be defined (at least initialized),
		// no matter wether they are wanted for filtering or not.
		$additional_filters_to_init = array('filter_keywords_filter_type',
		                                    'filter_result_result',
		                                    'filter_result_method',
		                                    'filter_result_build',
		                                    'filter_assigned_user_include_unassigned');
		
		// now nullify them
		foreach ($additional_filters_to_init as $filtername) {
			$this->active_filters[$filtername] = null;
		}
		

		// iterate through all filters and activate the needed ones
		$this->display_filters = false;

		foreach ($this->all_filters as $name => $info) {
			$init_method = "init_$name";
			// BUGID 3853
			if (in_array($name, $this->mode_filter_mapping[$this->mode]) &&
				method_exists($this, $init_method) && $this->configuration->{$name} == ENABLED &&
				$this->configuration->show_filters == ENABLED) {

				$this->$init_method();

				// there is at least one filter item to display => switch panel on
				$this->display_filters = true;

			} else {
				// is not needed, deactivate filter by setting it to false in main array
				// and of course also in active filters array
				$this->filters[$name] = false;
				$this->active_filters[$name] = null;
			}
		}

		// special situation: the assigned user filter is in plan mode only needed for one feature
		if ($this->mode == 'plan_mode' && $this->args->feature != 'tc_exec_assignment') {
			$this->settings['filter_assigned_user'] = false;
		}

		// add the important settings to active filter array
		foreach ($this->all_settings as $name => $info) {
			if ($this->settings[$name]) {
				$this->active_filters[$name] = $this->settings[$name]['selected'];
			} else {
				$this->active_filters[$name] = null;
			}
		}
	} // end of method

	/**
	 * This method returns an object or array, containing all selections chosen
	 * by the user for filtering.
	 * 
	 * @return mixed $value Return value is either an array or stdClass object,
	 * depending on active mode. It contains all filter values selected by the user.
	 */
	protected function get_active_filters() {
		static $value = null; // serves as a kind of cache
		                      // if method is called more than once
				
		// convert array to stcClass if needed
		if (!$value) {
			switch ($this->mode) {
				case 'execution_mode':
				case 'plan_mode':
					// these features are generating an exec tree,
					// they need the filters as a stdClass object
					$value = (object) $this->active_filters;
					break;
				
				default:
					// otherwise simply return the array as-is
					$value = $this->active_filters;
					break;
			}
		}
		
		return $value;
	} // end of method

	public function set_testcases_to_show($testcases_to_show = null) {
		// update active_filters
		if (!is_null($testcases_to_show)) {
			$this->active_filters['testcases_to_show'] = $testcases_to_show;
		}
		
		// Since a new filter in active_filters has been set from outside class after
		// saving of session data has already happened in constructor, 
		// we explicitly update data in session after this change here.
		$this->save_session_data();
	}
	
	/**
	 * Active filters will be saved to $_SESSION. 
	 * If there already is data for the active mode and token, it will be overwritten.
	 * This data will be read from pages in the right frame.
	 * This solves the problems with too long URLs.
	 * See issue 3516 in Mantis for a little bit more information/explanation.
	 * The therefore caused new problem that would arise now if
	 * a user uses the same feature simultaneously in multiple browser tabs
	 * is solved be the additional measure of using a form token.
	 * 
	 * @author Andreas Simon
	 * @return $tl::OK
	 */
	public function save_session_data() {		
		if (!isset($_SESSION[$this->mode]) || is_null($_SESSION[$this->mode]) || !is_array($_SESSION[$this->mode])) {
			$_SESSION[$this->mode] = array();
		}
		
		$_SESSION[$this->mode][$this->form_token] = $this->active_filters;
		$_SESSION[$this->mode][$this->form_token]['timestamp'] = time();
		
		return tl::OK;
	}
	
	/**
	 * Old filter data for active mode will be deleted from $_SESSION.
	 * It happens automatically after a session has expired and a user therefore
	 * has to log in again, but here we can configure an additional time limit
	 * only for this special filter part in session data.
	 * 
	 * @author Andreas Simon
	 * @param int $token_validity_duration data older than given timespan will be deleted
	 */
	public function delete_old_session_data($token_validity_duration = 0) {
		// TODO this duration could maybe also be configured in config/const.inc.php
		
		// how long shall the data remain in session before it will be deleted?
		if (!is_numeric($token_validity_duration) || $token_validity_duration <= 0) {
			$token_validity_duration = 60 * 60 * 1; // one hour as default
		}
		
		// delete all tokens from session that are older than given age
		if (isset($_SESSION[$this->mode]) && is_array($_SESSION[$this->mode])) {
			foreach ($_SESSION[$this->mode] as $token => $data) {
				if ($data['timestamp'] < (time() - $token_validity_duration)) {
					// too old, delete!
					unset($_SESSION[$this->mode][$token]);
				}
			}
		}
	}
	
	public function delete_own_session_data() {
		if (isset($_SESSION[$this->mode]) && isset($_SESSION[$this->mode][$this->form_token])) {
			unset($_SESSION[$this->mode][$this->form_token]);
		}
	}
	
	/**
	 * Generates a form token, which will be used to identify the relationship
	 * between left navigator-frame with its settings and right frame.
	 */
	protected function generate_form_token() {
		// Notice: I am just generating an integer here for the token.
		// Since this is not any security relevant stuff like a password hash or similar,
		// but only a means to separate multiple tabs a single user opens, this should suffice.
		// If we should some day decide that an integer is not enough,
		// we just have to change this one method and everything will still work.
		
		$min = 1234567890; // not magic, just some large number so the tokens don't get too short 
		$max = mt_getrandmax();
		$token = 0;
		
		// generate new tokens until we find one that doesn't exist yet
		do {
			$token = mt_rand($min, $max);
		} while (isset($_SESSION[$this->mode][$token]));
		
		$this->form_token = $token;
	}
	
	/**
	 * Active filters will be formatted as a GET-argument string.
	 * 
	 * @return string $string the formatted string with active filters
	 */
	public function get_argument_string() {
		static $string = null; // cache for repeated calls of this method
		
		if (!$string) {
			$string = '';

			// important: the token with which the page in right frame can access data in session
			$string .= '&form_token=' . $this->form_token;
			
			if ($this->settings['setting_build']) {
				$string .= '&setting_build=' . 
				           $this->settings['setting_build']['selected'];
			}
			
			if ($this->settings['setting_platform']) {
				$string .= '&setting_platform=' . 
				           $this->settings['setting_platform']['selected'];
			}
			
			$keyword_list = null;
			if (is_array($this->active_filters['filter_keywords'])) {
				$keyword_list = implode(',', $this->active_filters['filter_keywords']);
			} else if ($this->active_filters['filter_keywords']) {
				$keyword_list = $this->active_filters['filter_keywords'];
			}			
			if ($keyword_list) {
				$string .= '&filter_keywords=' . $keyword_list . 
				           '&filter_keywords_filter_type=' . 
				           $this->active_filters['filter_keywords_filter_type'];
			}
			
			if ($this->active_filters['filter_priority'] > 0) {
				$string .= '&filter_priority=' . $this->active_filters['filter_priority'];
			}
						
			if ($this->active_filters['filter_assigned_user']) {
				// 3630
				$unassigned = $this->active_filters['filter_assigned_user_include_unassigned'] ? '1' : '0';
				$string .= '&filter_assigned_user='. 
				           serialize($this->active_filters['filter_assigned_user']) .
				           '&filter_assigned_user_include_unassigned=' . $unassigned;
			}
			
			if ($this->active_filters['filter_result_result']) {
				$string .= '&filter_result_result=' .
				           serialize($this->active_filters['filter_result_result']) .
				           '&filter_result_method=' .
				           $this->active_filters['filter_result_method'] .
				           '&filter_result_build=' .
				           $this->active_filters['filter_result_build'];
			}
		}
		
		return $string;
	}
	
	/**
	 * Build the tree menu for generation of JavaScript test case tree.
	 * Depending on mode and user's selections in user interface, 
	 * either a completely filtered tree will be build and returned,
	 * or only the minimal necessary data to "lazy load" 
	 * the objects in the tree by later Ajax calls.
	 * No return value - all variables will be stored in gui object
	 * which is passed by reference.
	 * 
	 * @author Andreas Simon
	 * @param object $gui Reference to GUI object (data will be written to it)
	 */
	public function build_tree_menu(&$gui) {
		
		$tree_menu = null;
		$filters = $this->get_active_filters();
		$additional_info = null;
		$options = null;
		$loader = '';
		$children = "[]";
		$cookie_prefix = '';
		
		// by default, disable drag and drop, then later enable if needed
		$drag_and_drop = new stdClass();
		$drag_and_drop->enabled = false;
		$drag_and_drop->BackEndUrl = '';
		$drag_and_drop->useBeforeMoveNode = FALSE;
				
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		
		$tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
					
		switch ($this->mode) {
			
			case 'plan_mode':
				// No lazy loading here.
					
				$additional_info = new stdClass();
				$additional_info->useCounters = CREATE_TC_STATUS_COUNTERS_OFF;
				$additional_info->useColours = COLOR_BY_TC_STATUS_OFF;
				$additional_info->testcases_colouring_by_selected_build = DISABLED;
				
				$filters->show_testsuite_contents = 1;
				$filters->hide_testcases = 0;
	
				if ($this->args->feature == 'test_urgency') {
					$filters->hide_testcases = 1;
				}
				
				list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
		                                                       $gui->menuUrl,
		                                                       $this->args->testproject_id,
		                                                       $this->args->testproject_name,
		                                                       $this->args->testplan_id,
		                                                       $this->args->testplan_name,
		                                                       $filters,
		                                                       $additional_info);
				
				$this->set_testcases_to_show($testcases_to_show);
				
				$root_node = $tree_menu->rootnode;
				$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				$cookie_prefix = $this->args->feature;
			break;
			
			case 'edit_mode':
				
				if ($gui->tree_drag_and_drop_enabled[$this->args->feature]) {
					$drag_and_drop->enabled = true;
					$drag_and_drop->BackEndUrl = $this->args->basehref . 
					                             'lib/ajax/dragdroptprojectnodes.php';
					$drag_and_drop->useBeforeMoveNode = false;
				}
									
				if ($this->do_filtering) {
					$options = array('forPrinting' => NOT_FOR_PRINTING,
					                 'hideTestCases' => SHOW_TESTCASES,
						             'tc_action_enabled' => DO_ON_TESTCASE_CLICK,
						             'ignore_inactive_testcases' => DO_NOT_FILTER_INACTIVE_TESTCASES,
					                 'exclude_branches' => null);
				    
					$tree_menu = generateTestSpecTree($this->db, $this->args->testproject_id,
					                                  $this->args->testproject_name,
					                                  $gui->menuUrl, $filters, $options);
					
					$root_node = $tree_menu->rootnode;
					$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
					$cookie_prefix = $this->args->feature;
				} else {
					$loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
					          "root_node={$this->args->testproject_id}&" .
					          "tcprefix=" . urlencode($tc_prefix .
					          $this->configuration->tc_cfg->glue_character);
					
					$tcase_qty = $this->testproject_mgr->count_testcases($this->args->testproject_id);
					
					$root_node = new stdClass();
					$root_node->href = "javascript:EP({$this->args->testproject_id})";
					$root_node->id = $this->args->testproject_id;
					$root_node->name = $this->args->testproject_name . " ($tcase_qty)";
					$root_node->testlink_node_type='testproject';
													
					$cookie_prefix = 'tproject_' . $root_node->id . "_";
				}
			break;
			
			case 'plan_add_mode':
				
				$cookie_prefix = "planaddtc_{$this->args->testproject_id}_{$this->args->user_id}_";
				
				if ($this->do_filtering) {
					$options = array('forPrinting' => NOT_FOR_PRINTING,
					                 'hideTestCases' => HIDE_TESTCASES,
					                 'tc_action_enabled' => ACTION_TESTCASE_DISABLE,
					                 'ignore_inactive_testcases' => IGNORE_INACTIVE_TESTCASES,
					                 'viewType' => 'testSpecTreeForTestPlan');
			
					$tree_menu = generateTestSpecTree($this->db,
					                                  $this->args->testproject_id,
					                                  $this->args->testproject_name,
					                                  $gui->menuUrl,
					                                  $filters,
					                                  $options);
					
					$root_node = $tree_menu->rootnode;
				    $children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				} else {
					$loader = $this->args->basehref . 'lib/ajax/gettprojectnodes.php?' .
					                    "root_node={$this->args->testproject_id}&show_tcases=0";
				
					$root_node = new stdClass();
					$root_node->href = "javascript:EP({$this->args->testproject_id})";
					$root_node->id = $this->args->testproject_id;
					$root_node->name = $this->args->testproject_name;
					$root_node->testlink_node_type = 'testproject';
				}
			break;
			
			case 'execution_mode':
			default:
				// No lazy loading here.
				// Filtering is always done in execution mode, no matter if user enters data or not,
				// since the user should usually never see the whole tree here.
				$additional_info = new stdClass();
				$filters->hide_testcases = false;
				$filters->show_testsuite_contents = $this->configuration->exec_cfg->show_testsuite_contents;
				$additional_info->useCounters = $this->configuration->exec_cfg->enable_tree_testcase_counters;
				
				$additional_info->useColours = new stdClass();
				$additional_info->useColours->testcases = 
					$this->configuration->exec_cfg->enable_tree_testcases_colouring;
				$additional_info->useColours->counters = 
					$this->configuration->exec_cfg->enable_tree_counters_colouring;
				$additional_info->testcases_colouring_by_selected_build =
					$this->configuration->exec_cfg->testcases_colouring_by_selected_build; 
					
				list($tree_menu, $testcases_to_show) = generateExecTree($this->db,
				                                                        $gui->menuUrl,
				                                                        $this->args->testproject_id,
				                                                        $this->args->testproject_name,
				                                                        $this->args->testplan_id,
				                                                        $this->args->testplan_name,
				                                                        $filters,
				                                                        $additional_info);
					
				$this->set_testcases_to_show($testcases_to_show);
				
				$root_node = $tree_menu->rootnode;
				$children = $tree_menu->menustring ? $tree_menu->menustring : "[]";
				$cookie_prefix = 'exec_tplan_id_' . $this->args->testplan_id;
			break;
		}
		
		$gui->tree = $tree_menu;
		
		$gui->ajaxTree = new stdClass();
		$gui->ajaxTree->loader = $loader;
		$gui->ajaxTree->root_node = $root_node;
		$gui->ajaxTree->children = $children;
		$gui->ajaxTree->cookiePrefix = $cookie_prefix;
		$gui->ajaxTree->dragDrop = $drag_and_drop;
	} // end of method
	
	private function init_setting_refresh_tree_on_action() {

		$key = 'setting_refresh_tree_on_action';
		$hidden_key = 'hidden_setting_refresh_tree_on_action';
		$selection = 0;

		$this->settings[$key] = array();
		$this->settings[$key][$hidden_key] = false;

		// look where we can find the setting - POST, SESSION, config?
		if (isset($this->args->{$key})) {
			$selection = 1;
		} else if (isset($this->args->{$hidden_key})) {
			$selection = 0;
		} else if (isset($_SESSION[$key])) {
			$selection = $_SESSION[$key];
		} else {
			$spec_cfg = config_get('spec_cfg');
			$selection = ($spec_cfg->automatic_tree_refresh > 0) ? 1 : 0;
		}
		
		$this->settings[$key]['selected'] = $selection;
		$this->settings[$key][$hidden_key] = $selection;
		$_SESSION[$key] = $selection;		
	} // end of method

	private function init_setting_build() {

		$key = 'setting_build';
		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}

		$tplan_id = $this->settings['setting_testplan']['selected'];

		// when in plan mode (assigning execution), we want all builds,
		// otherwise only those which are active and open
		$active = ($this->mode == 'plan_mode') ? null : testplan::GET_ACTIVE_BUILD;
		$open = ($this->mode == 'plan_mode') ? null : testplan::GET_OPEN_BUILD;
		
		$this->settings[$key]['items'] = $this->testplan_mgr->get_builds_for_html_options($tplan_id, $active, $open);
		$tplan_builds = array_keys($this->settings[$key]['items']);

		// BUGID 3406 - depending on mode, we need different labels for this selector on GUI
		$label = ($this->mode == 'plan_mode') ? 'assign_build' : 'exec_build';
		$this->settings[$key]['label'] = lang_get($label);
		
		// if no build has been chosen by user, select newest build by default
		$newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, $active, $open);

		// BUGID 3726
		$session_key = $tplan_id . '_stored_setting_build';
		$session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;

		$this->args->{$key} = $this->args->{$key} > 0 ? $this->args->{$key} : $session_selection;

		if (!$this->args->{$key}) {
			$this->args->{$key} = $newest_build_id;
		}
		
		// only take build ID into account if it really is a build from this testplan
		$this->settings[$key]['selected'] = (in_array($this->args->{$key}, $tplan_builds)) ? 
		                                    $this->args->{$key} : $newest_build_id;

		// still no build selected? take first one from selection.
		if (!$this->settings[$key]['selected'] && sizeof($this->settings[$key]['items'])) {
			$this->settings[$key]['selected'] = end($tplan_builds);
		}

		$_SESSION[$session_key] = $this->settings[$key]['selected'];
	} // end of method

	private function init_setting_testplan() {

		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}
		
		$key = 'setting_testplan';
		$testplans = $this->user->getAccessibleTestPlans($this->db, $this->args->testproject_id);

		if (isset($_SESSION['testplanID']) && $_SESSION['testplanID'] != $this->args->{$key}) {
			// testplan was changed, we need to reset all filters
			// --> they were chosen for another testplan, not this one!
			$this->args->reset_filters = true;

			// check if user is allowed to set chosen testplan before changing
			foreach ($testplans as $plan) {
				if ($plan['id'] == $this->args->{$key}) {
					setSessionTestPlan($plan);
				}
			}
		}

		// now load info from session
		$info = $this->testplan_mgr->get_by_id($_SESSION['testplanID']);
		$this->args->testplan_name = $info['name'];
		$this->args->testplan_id = $info['id'];
		$this->args->{$key} = $info['id'];
		$this->settings[$key]['selected'] = $info['id'];

		// Now get all selectable testplans for the user to display.
		// For execution, don't take testplans into selection which have no (active/open) builds!
		// For plan add mode, add every plan no matter if he has builds or not.
		foreach ($testplans as $plan) {
			$add_plan = $this->mode == 'plan_add_mode';
			if (!$add_plan) {
				$builds = $this->testplan_mgr->get_builds($plan['id'],
				                                          testplan::GET_ACTIVE_BUILD,
				                                          testplan::GET_OPEN_BUILD);
				$add_plan =  (is_array($builds) && count($builds));
			}
			
			if ($add_plan) {
				$this->settings[$key]['items'][$plan['id']] = $plan['name'];
			}
		}
	} // end of method

	private function init_setting_platform() {
		if (!$this->platform_mgr) {
			$this->platform_mgr = new tlPlatform($this->db);
		}
		$key = 'setting_platform';

		$this->settings[$key] = array('items' => null, 'selected' => $this->args->{$key});
		$testplan_id = $this->settings['setting_testplan']['selected'];

		$this->settings[$key]['items'] = $this->platform_mgr->getLinkedToTestplanAsMap($testplan_id);

		// BUGID 3726
		$session_key = $testplan_id . '_stored_setting_platform';
		$session_selection = isset($_SESSION[$session_key]) ? $_SESSION[$session_key] : null;

		if (!$this->settings[$key]['selected']) {
			$this->settings[$key]['selected'] = $session_selection;
		}

		if (!isset($this->settings[$key]['items']) || !is_array($this->settings[$key]['items'])) {
			$this->settings[$key] = false;
		} else if (isset($this->settings[$key]['items']) && is_array($this->settings[$key]['items']) && 
				   is_null($this->settings[$key]['selected'])) {
					// platforms exist, but none has been selected yet, so select first one
			$this->settings[$key]['selected'] =	key($this->settings[$key]['items']);
		}

		$_SESSION[$session_key] = $this->settings[$key]['selected'];
	} // end of method

	private function init_filter_tc_id() {
		$key = 'filter_tc_id';
		$selection = $this->args->{$key};
		$internal_id = null;
		
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		if (!$this->tc_mgr) {
			$this->tc_mgr = new testcase($this->db);
		}
		
		$tc_prefix = $this->testproject_mgr->getTestCasePrefix($this->args->testproject_id);
		$tc_prefix .= $this->configuration->tc_cfg->glue_character;
		
		if (!$selection || $selection == $tc_prefix || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
			// we got the external ID here when filtering, but need the internal one
			$internal_id = $this->tc_mgr->getInternalID($selection);
		}
		
		$this->filters[$key] = array('selected' => $selection ? $selection : $tc_prefix);
		$this->active_filters[$key] = $internal_id;
	} // end of method
	
	private function init_filter_testcase_name() {
		$key = 'filter_testcase_name';
		$selection = $this->args->{$key};
		
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		$this->filters[$key] = array('selected' => $selection);
		$this->active_filters[$key] = $selection;
	} // end of method


	private function init_filter_toplevel_testsuite() {
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		$key = 'filter_toplevel_testsuite';
			
		$first_level_suites = $this->testproject_mgr->get_first_level_test_suites($this->args->testproject_id,
		                                                                          'smarty_html_options');
		
		$selection = $this->args->{$key};
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		// this filter should only be visible if there are any top level testsuites
		$this->filters[$key] = null; // BUGID 3660
		if ($first_level_suites) {			
			$this->filters[$key] = array('items' => array(0 => ''),
			                             'selected' => $selection,
			                             'exclude_branches' => array());
		
			foreach ($first_level_suites as $suite_id => $suite_name) {
				$this->filters[$key]['items'][$suite_id] = $suite_name;
								
				if ($selection && $suite_id != $selection) {
					$this->filters[$key]['exclude_branches'][$suite_id] = 'exclude_me';
				}
			}
			
			// Important: This is the only case in which active_filters contains the items
			// which have to be deleted from tree, instead of the other way around.
			$this->active_filters[$key] = $this->filters[$key]['exclude_branches'];
		} else {
			$this->active_filters[$key] = null;
		}		
	} // end of method

	private function init_filter_keywords() {
		$key = 'filter_keywords';
		$type = 'filter_keywords_filter_type';
		$this->filters[$key] = false;
		$keywords = null;

		switch ($this->mode) {
			case 'edit_mode':
			// BUGID 3822
			case 'plan_add_mode':
				// we need the keywords for the whole testproject
				if (!$this->testproject_mgr) {
					$this->testproject_mgr = new testproject($this->db);
				}
				$keywords = $this->testproject_mgr->get_keywords_map($this->args->testproject_id);
				break;

			default:
				// otherwise (not in edit mode), we want only keywords assigned to testplan
				if (!$this->testplan_mgr) {
					$this->testplan_mgr = new testplan($this->db);
				}
				$tplan_id = $this->settings['setting_testplan']['selected'];
				$keywords = $this->testplan_mgr->get_keywords_map($tplan_id, ' ORDER BY keyword ');
				break;
		}

		$selection = $this->args->{$key};
		$type_selection = $this->args->{$type};
		
		// are there any keywords?
		if (!is_null($keywords) && count($keywords)) {
			$this->filters[$key] = array();

			if (!$selection || !$type_selection || $this->args->reset_filters) {
				// default values for filter reset
				$selection = null;
				$type_selection = 'Or';
			} else {
				$this->do_filtering = true;
			}
			
			// data for the keywords themselves
			$this->filters[$key]['items'] = array($this->option_strings['any']) + $keywords;
			$this->filters[$key]['selected'] = $selection;
			$this->filters[$key]['size'] = min(count($this->filters[$key]['items']),
			                                   self::ADVANCED_FILTER_ITEM_QUANTITY);

			// additional data for the filter type (logical and/or)
			$this->filters[$key][$type] = array();
			$this->filters[$key][$type]['items'] = array('Or' => lang_get('logical_or'),
			                                                     'And' => lang_get('logical_and'));
			$this->filters[$key][$type]['selected'] = $type_selection;
		}
		
		// set the active value to filter
		// delete keyword filter if "any" (0) is part of the selection - regardless of filter mode
		if (is_array($this->filters[$key]['selected'])
		&& in_array(0, $this->filters[$key]['selected'])) {
			$this->active_filters[$key] = null;
		} else {
			$this->active_filters[$key] = $this->filters[$key]['selected'];
		}
		$this->active_filters[$type] = $selection ? $type_selection : null;
	} // end of method

	private function init_filter_priority() {
		// This is a special case of filter: the menu items don't get initialized here,
		// they are available as a global smarty variable. So the only thing to be managed
		// here is the selection by user.
		$key = 'filter_priority';
		
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}
		
		$tp_info = $this->testproject_mgr->get_by_id($this->args->testproject_id);
		$enabled = $tp_info['opt']->testPriorityEnabled;
				
		$this->active_filters[$key] = null;
		$this->filters[$key] = false;
		
		// BUGID 3910: show filter only if test priority management is enabled
		if ($enabled) {
			// default value and filter reset
			$selection = $this->args->{$key};
			if (!$selection || $this->args->reset_filters) {
				$selection = null;
			} else {
				$this->do_filtering = true;
			}
	
			$this->filters[$key] = array('selected' => $selection);
			$this->active_filters[$key] = $selection;
		}		
	} // end of method

	private function init_filter_execution_type() {
		if (!$this->tc_mgr) {
			$this->tc_mgr = new testcase($this->db);
		}
		$key = 'filter_execution_type';

		$selection = $this->args->{$key};
		// handle filter reset
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}
		
		$this->filters[$key] = array('items' => array(), 'selected' => $selection);

		// load available execution types
		$this->filters[$key]['items'] = $this->tc_mgr->get_execution_types();
		// add "any" string to these types at index 0 as default selection
		$this->filters[$key]['items'] = array(0 => $this->option_strings['any'])
		                                      + $this->filters[$key]['items'];
		
		$this->active_filters[$key] = $selection;
	} // end of method

	private function init_filter_assigned_user() {
		if (!$this->testproject_mgr) {
			$this->testproject_mgr = new testproject($this->db);
		}

		$key = 'filter_assigned_user';
		$unassigned_key = 'filter_assigned_user_include_unassigned';
		$tplan_id = $this->settings['setting_testplan']['selected'];

		// set selection to default (any), only change if value is sent by user and reset is not requested
		$selection = $this->args->{$key};
		if (!$selection || $this->args->reset_filters) {
			$selection = null;
		} else {
			$this->do_filtering = true;
		}

		$tproject_info = $this->testproject_mgr->get_by_id($this->args->testproject_id);

		$all_testers = getTestersForHtmlOptions($this->db, $tplan_id, $tproject_info, null,
			                                    array(TL_USER_ANYBODY => $this->option_strings['any'],
			                                          TL_USER_NOBODY => $this->option_strings['none'],
			                                          TL_USER_SOMEBODY => $this->option_strings['somebody']),
			                                    'any');
		$visible_testers = $all_testers;
		
		// in execution mode the rights of the user have to be regarded
		if ($this->mode == 'execution_mode') {
			$role = $this->user->getEffectiveRole($this->db, $this->args->testproject_id, $tplan_id);
			
			$simple_tester_roles = array_flip($this->configuration->exec_cfg->simple_tester_roles);
			
			// check the user's rights to see what he may do
			$right_to_execute = $role->hasRight('testplan_execute');
			$right_to_manage = $role->hasRight('testplan_planning');
			
			$simple = false;
			if (isset($simple_tester_roles[$role->dbID]) || ($right_to_execute && !$right_to_manage)) {
				// user is only simple tester and may not see/execute everything
				$simple = true;
			}

			$view_mode = $simple ? $this->configuration->exec_cfg->view_mode->tester : 'all';
			
			if ($view_mode != 'all') {
				$visible_testers = (array)$this->user->getDisplayName();
				$selection = (array)$this->user->dbID;
			}

			// re-enable option "user_filter_default"
			if (!$selection && $this->configuration->exec_cfg->user_filter_default == 'logged_user') {
				$selection = (array)$this->user->dbID;
			}
		}
		
		$this->filters[$key] = array('items' => $visible_testers,
		                             'selected' => $selection,
		                             $unassigned_key => $this->args->{$unassigned_key});
		
		// which value shall be passed to tree generation class?
		
		if ((is_array($selection) && in_array(TL_USER_ANYBODY, $selection))
		|| ($selection == TL_USER_ANYBODY)) {
			// delete user assignment filter if "any user" is part of the selection
			$this->active_filters[$key] = null;
			$this->active_filters[$unassigned_key] = 0;
		}
		
		if (is_array($selection)) {
			// get keys of the array as values
			$this->active_filters[$key] = array_flip($selection);
			foreach ($this->active_filters[$key] as $user_key => $user_value) {
				$this->active_filters[$key][$user_key] = $user_key;
			}
			$this->active_filters[$unassigned_key] = $this->filters[$key][$unassigned_key];
		}
	} // end of method

	private function init_filter_custom_fields() {
		$key = 'filter_custom_fields';
		$no_warning = true;
		
		// BUGID 3930
		global $g_locales_date_format;
		$locale = (isset($_SESSION['locale'])) ? $_SESSION['locale'] : 'en_GB';
		$date_format = str_replace('%', '', $g_locales_date_format[$locale]);
		
		// BUGID 3566: show/hide CF
		$collapsed = isset($_SESSION['cf_filter_collapsed']) ? $_SESSION['cf_filter_collapsed'] : 0;
		$collapsed = isset($_REQUEST['btn_toggle_cf']) ? !$collapsed : $collapsed;
		$_SESSION['cf_filter_collapsed'] = $collapsed;	
		$btn_label = $collapsed ? lang_get('btn_show_cf') : lang_get('btn_hide_cf');
		
		if (!$this->cfield_mgr) {
			$this->cfield_mgr = new cfield_mgr($this->db, $this->args->testproject_id);
		}
		
		$cfields = $this->cfield_mgr->get_linked_cfields_at_design($this->args->testproject_id, 1, null, 'testcase');
		$cf_prefix = $this->cfield_mgr->name_prefix;
		$cf_html_code = "";
		$selection = array();
		
		$this->filters[$key] = false;
		$this->active_filters[$key] = null;

		if (!is_null($cfields)) {
			// display and compute only when custom fields are in use
			foreach ($cfields as $cf_id => $cf) {
				// has a value been selected?
				$id = $cf['id'];
				$type = $cf['type'];
				$verbose_type = trim($this->cfield_mgr->custom_field_types[$type]);
				$cf_input_name = "{$cf_prefix}{$type}_{$id}";
				
				// BUGID 3716
				// custom fields on test spec did not retain value after apply
				$value = isset($_REQUEST[$cf_input_name]) ? $_REQUEST[$cf_input_name] : null;

				// BUGID 3884: added filtering for datetime custom fields
				if ($verbose_type == 'datetime') {
					// if cf is a date field, convert the three given values to unixtime format
					if (isset($_REQUEST[$cf_input_name . '_input']) && $_REQUEST[$cf_input_name . '_input'] != ''
					&& isset($_REQUEST[$cf_input_name . '_hour']) && $_REQUEST[$cf_input_name . '_hour'] != ''
					&& isset($_REQUEST[$cf_input_name . '_minute']) && $_REQUEST[$cf_input_name . '_minute'] != ''
					&& isset($_REQUEST[$cf_input_name . '_second']) && $_REQUEST[$cf_input_name . '_second'] != '') {
						$date = $_REQUEST[$cf_input_name . '_input'];
						
						$hour = $_REQUEST[$cf_input_name . '_hour'];
						$minute = $_REQUEST[$cf_input_name . '_minute'];
						$second = $_REQUEST[$cf_input_name . '_second'];

						$date_array = split_localized_date($date, $date_format);
						$value = mktime($hour, $minute, $second, $date_array['month'], $date_array['day'], $date_array['year']);
					}
				}

				if ($verbose_type == 'date') {
					// if cf is a date field, convert the three given values to unixtime format
					// BUGID 3883: only set values if different from 0
					if (isset($_REQUEST[$cf_input_name . '_input']) && $_REQUEST[$cf_input_name . '_input'] != '') {
						$date = $_REQUEST[$cf_input_name . '_input'];						
						$date_array = split_localized_date($date, $date_format);
						$value = mktime(0, 0, 0, $date_array['month'], $date_array['day'], $date_array['year']);
					}
				}

				if ($this->args->reset_filters) {
					$value = null;
				}

				$value2display = $value;
				if (!is_null($value2display) && is_array($value2display)){
					$value2display = implode("|", $value2display);
				}
				$cf['value'] = $value2display;

				if ($value) {
					$this->do_filtering = true;
					$selection[$id] = $value;
				}

				$label = str_replace(TL_LOCALIZE_TAG, '', lang_get($cf['label'],
				                                                   null, $no_warning));

				$cf_size = self::CF_INPUT_SIZE;
				// set special size for list inputs
				if ($verbose_type == 'list' || $verbose_type == 'multiselection list') {
					$cf_size = 3;
				}
				
				// don't show textarea inputs here, they are too large for filterpanel
				if ($verbose_type != 'text area') {
					$cf_html_code .= '<tr class="cfRow"><td>' . htmlspecialchars($label) . '</td><td>' .
					                 $this->cfield_mgr->string_custom_field_input($cf, '', $cf_size, true) .
					                 '</td></tr>';
				}
			}

			// BUGID 3566: show/hide CF
			$this->filters[$key] = array('items' => $cf_html_code, 
			                             'btn_label' => $btn_label, 
			                             'collapsed' => $collapsed);
			$this->active_filters[$key] = count($selection) ? $selection : null;
		}
	} // end of method

	private function init_filter_result() {
		$key = 'filter_result';
		$result_key = 'filter_result_result';
		$method_key = 'filter_result_method';
		$build_key = 'filter_result_build';
		
		if (is_null($this->testplan_mgr)) {
			$this->testplan_mgr = new testplan($this->db);
		}
		$tplan_id = $this->settings['setting_testplan']['selected'];

		$this->configuration->results = config_get('results');

		// determine, which config to load and use for filter methods - depends on mode!
		$cfg = ($this->mode == 'execution_mode') ? 
		       'execution_filter_methods' : 'execution_assignment_filter_methods';
		$this->configuration->filter_methods = config_get($cfg);

		// determin which filter method shall be selected by the JS function in template,
		// when only one build is selectable by the user
		$js_key_to_select = 0;
		if ($this->mode == 'execution_mode') {
			$js_key_to_select = $this->configuration->filter_methods['status_code']['current_build'];
		} else if ($this->mode == 'plan_mode') {
			$js_key_to_select = $this->configuration->filter_methods['status_code']['specific_build'];
		}
		
		// values selected by user
		$result_selection = $this->args->{$result_key};
		$method_selection = $this->args->{$method_key};
		$build_selection = $this->args->{$build_key};

		// default values
		$default_filter_method = $this->configuration->filter_methods['default_type'];
		$any_result_key = $this->configuration->results['status_code']['all'];
		$newest_build_id = $this->testplan_mgr->get_max_build_id($tplan_id, testplan::GET_ACTIVE_BUILD);

		// BUGID 3817
		if (is_null($method_selection)) {
			$method_selection = $default_filter_method;
		}

		if (is_null($result_selection) || $this->args->reset_filters) {
			// no selection yet or filter reset requested
			$result_selection = $any_result_key;
			$method_selection = $default_filter_method;
			$build_selection = $newest_build_id;
		} else {
			$this->do_filtering = true;
		}
		
		// init array structure
		$this->filters[$key] = array($result_key => array('items' => null,
		                                                  'selected' => $result_selection),
		                             $method_key => array('items' => array(),
		                                                  'selected' => $method_selection,
		                                                  'js_selection' => $js_key_to_select),
		                             $build_key => array('items' => null,
		                                                 'selected' => $build_selection));

		// init menu for result selection by function from exec.inc.php
		$this->filters[$key][$result_key]['items'] = createResultsMenu();
		$this->filters[$key][$result_key]['items'][$any_result_key] = $this->option_strings['any'];

		// init menu for filter method selection
		foreach ($this->configuration->filter_methods['status_code'] as $statusname => $statusshortcut) {
			$code = $this->configuration->filter_methods['status_code'][$statusname];
			$this->filters[$key][$method_key]['items'][$code] =
				lang_get($this->configuration->filter_methods['status_label'][$statusname]);
		}
		
		// init menu for build selection
		$this->filters[$key][$build_key]['items'] =
			$this->testplan_mgr->get_builds_for_html_options($tplan_id, testplan::GET_ACTIVE_BUILD);
		
		// if "any" is selected, nullify the active filters
		if ((is_array($result_selection) && in_array($any_result_key, $result_selection))
		|| $result_selection == $any_result_key) {
			$this->active_filters[$result_key] = null;
			$this->active_filters[$method_key] = null;
			$this->active_filters[$build_key] = null;
			$this->filters[$key][$result_key]['selected'] = $any_result_key;
			$this->filters[$key][$method_key]['selected'] = $default_filter_method;
			$this->filters[$key][$build_key]['selected'] = $newest_build_id;
		} else {
			$this->active_filters[$result_key] = $result_selection;
			$this->active_filters[$method_key] = $method_selection;
			$this->active_filters[$build_key] = $build_selection;
		}
	} // end of method
} // end of class tlTestCaseFilterControl
?>